/*
 * Copyright (c) 1997, 2007, Oracle and/or its affiliates. All rights reserved.
 * ORACLE PROPRIETARY/CONFIDENTIAL. Use is subject to license terms.
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 *
 */

package com.sun.jmx.snmp;

/**
 * The <CODE>BerDecoder</CODE> class is used for decoding
 * BER-encoded data.
 *
 * A <CODE>BerDecoder</CODE> needs to be set up with the byte string containing
 * the encoding. It maintains a current position in the byte string.
 *
 * Methods allows to fetch integer, string, OID, etc., from the current
 * position. After a fetch the current position is moved forward.
 *
 * A fetch throws a <CODE>BerException</CODE> if the encoding is not of the
 * expected type.
 *
 * <p><b>This API is a Sun Microsystems internal API  and is subject
 * to change without notice.</b></p>
 *
 * @since 1.5
 */

public class BerDecoder {

    /**
    * Constructs a new decoder and attaches it to the specified byte string.
    *
    * @param b The byte string containing the encoded data.
    */

    public BerDecoder(byte b[]) {
        bytes = b;
        reset();
    }

    public void reset() {
        next = 0;
        stackTop = 0;
    }

    /**
    * Fetch an integer.
    *
    * @return The decoded integer.
    *
    * @exception BerException Current position does not point to an integer.
    */

    public int fetchInteger() throws BerException {
        return fetchInteger(IntegerTag);
    }

    /**
    * Fetch an integer with the specified tag.
    *
    * @param tag The expected tag.
    *
    * @return The decoded integer.
    *
    * @exception BerException Current position does not point to an integer
    *                         or the tag is not the expected one.
    */

    public int fetchInteger(int tag) throws BerException {
        int result = 0;
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            result = fetchIntegerValue();
        } catch (BerException e) {
            next = backup;
            throw e;
        }

        return result;
    }

    /**
    * Fetch an integer and return a long value.
    *
    * @return The decoded integer.
    *
    * @exception BerException Current position does not point to an integer.
    */

    public long fetchIntegerAsLong() throws BerException {
        return fetchIntegerAsLong(IntegerTag);
    }

    /**
    * Fetch an integer with the specified tag and return a long value.
    *
    * @param tag The expected tag.
    *
    * @return The decoded integer.
    *
    * @exception BerException Current position does not point to an integer
    *                         or the tag is not the expected one.
    */

    public long fetchIntegerAsLong(int tag) throws BerException {
        long result = 0;
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            result = fetchIntegerValueAsLong();
        } catch (BerException e) {
            next = backup;
            throw e;
        }

        return result;
    }

    /**
    * Fetch an octet string.
    *
    * @return The decoded string.
    *
    * @exception BerException Current position does not point to an octet string.
    */

    public byte[] fetchOctetString() throws BerException {
        return fetchOctetString(OctetStringTag);
    }

    /**
    * Fetch an octet string with a specified tag.
    *
    * @param tag The expected tag.
    *
    * @return The decoded string.
    *
    * @exception BerException Current position does not point to an octet string
    *                         or the tag is not the expected one.
    */

    public byte[] fetchOctetString(int tag) throws BerException {
        byte[] result = null;
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            result = fetchStringValue();
        } catch (BerException e) {
            next = backup;
            throw e;
        }

        return result;
    }

    /**
    * Fetch an object identifier.
    *
    * @return The decoded object identifier as an array of long.
    */

    public long[] fetchOid() throws BerException {
        return fetchOid(OidTag);
    }

    /**
    * Fetch an object identifier with a specified tag.
    *
    * @param tag The expected tag.
    *
    * @return The decoded object identifier as an array of long.
    *
    * @exception BerException Current position does not point to an oid
    *                         or the tag is not the expected one.
    */

    public long[] fetchOid(int tag) throws BerException {
        long[] result = null;
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            result = fetchOidValue();
        } catch (BerException e) {
            next = backup;
            throw e;
        }

        return result;
    }

    /**
    * Fetch a <CODE>NULL</CODE> value.
    *
    * @exception BerException Current position does not point to <CODE>NULL</CODE> value.
    */

    public void fetchNull() throws BerException {
        fetchNull(NullTag);
    }

    /**
    * Fetch a <CODE>NULL</CODE> value with a specified tag.
    *
    * @param tag The expected tag.
    *
    * @exception BerException Current position does not point to
    *            <CODE>NULL</CODE> value or the tag is not the expected one.
    */

    public void fetchNull(int tag) throws BerException {
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            final int length = fetchLength();
            if (length != 0)
                throw new BerException();
        } catch (BerException e) {
            next = backup;
            throw e;
        }
    }

    /**
    * Fetch an <CODE>ANY</CODE> value. In fact, this method does not decode anything
    * it simply returns the next TLV as an array of bytes.
    *
    * @return The TLV as a byte array.
    *
    * @exception BerException The next TLV is really badly encoded...
    */

    public byte[] fetchAny() throws BerException {
        byte[] result = null;
        final int backup = next;
        try {
            final int tag = fetchTag();
            final int contentLength = fetchLength();
            if (contentLength < 0)
                throw new BerException();
            final int tlvLength = next + contentLength - backup;
            if (contentLength > (bytes.length - next))
                throw new IndexOutOfBoundsException("Decoded length exceeds buffer");
            final byte[] data = new byte[tlvLength];
            java.lang.System.arraycopy(bytes, backup, data, 0, tlvLength);
            // for (int i = 0 ; i < tlvLength ; i++) {
            //  data[i] = bytes[backup + i] ;
            // }
            next = next + contentLength;
            result = data;
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        }
        // catch(Error e) {
        //    debug("fetchAny: Error decoding BER: " + e);
        //    throw e;
        // }

        return result;
    }

    /**
    * Fetch an <CODE>ANY</CODE> value with a specific tag.
    *
    * @param tag The expected tag.
    *
    * @return The TLV as a byte array.
    *
    * @exception BerException The next TLV is really badly encoded...
    */

    public byte[] fetchAny(int tag) throws BerException {
        if (getTag() != tag) {
            throw new BerException();
        }
        return fetchAny();
    }

    /**
    * Fetch a sequence header.
    * The decoder computes the end position of the sequence and push it
    * on its stack.
    *
    * @exception BerException Current position does not point to a sequence header.
    */

    public void openSequence() throws BerException {
        openSequence(SequenceTag);
    }

    /**
    * Fetch a sequence header with a specific tag.
    *
    * @param tag The expected tag.
    *
    * @exception BerException Current position does not point to a sequence header
    *                         or the tag is not the expected one.
    */

    public void openSequence(int tag) throws BerException {
        final int backup = next;
        try {
            if (fetchTag() != tag) {
                throw new BerException();
            }
            final int l = fetchLength();
            if (l < 0)
                throw new BerException();
            if (l > (bytes.length - next))
                throw new BerException();
            stackBuf[stackTop++] = next + l;
        } catch (BerException e) {
            next = backup;
            throw e;
        }
    }

    /**
    * Close a sequence.
    * The decode pull the stack and verifies that the current position
    * matches with the calculated end of the sequence. If not it throws
    * an exception.
    *
    * @exception BerException The sequence is not expected to finish here.
    */

    public void closeSequence() throws BerException {
        if (stackBuf[stackTop - 1] == next) {
            stackTop--;
        } else {
            throw new BerException();
        }
    }

    /**
    * Return <CODE>true</CODE> if the end of the current sequence is not reached.
    * When this method returns <CODE>false</CODE>, <CODE>closeSequence</CODE> can (and must) be
    * invoked.
    *
    * @return <CODE>true</CODE> if there is still some data in the sequence.
    */

    public boolean cannotCloseSequence() {
        return (next < stackBuf[stackTop - 1]);
    }

    /**
    * Get the tag of the data at the current position.
    * Current position is unchanged.
    *
    * @return The next tag.
    */

    public int getTag() throws BerException {
        int result = 0;
        final int backup = next;
        try {
            result = fetchTag();
        } finally {
            next = backup;
        }

        return result;
    }

    public String toString() {
        final StringBuffer result = new StringBuffer(bytes.length * 2);
        for (int i = 0; i < bytes.length; i++) {
            final int b = (bytes[i] > 0) ? bytes[i] : bytes[i] + 256;
            if (i == next) {
                result.append("(");
            }
            result.append(Character.forDigit(b / 16, 16));
            result.append(Character.forDigit(b % 16, 16));
            if (i == next) {
                result.append(")");
            }
        }
        if (bytes.length == next) {
            result.append("()");
        }

        return new String(result);
    }

    //
    // Some standard tags
    //
    public final static int BooleanTag = 1;
    public final static int IntegerTag = 2;
    public final static int OctetStringTag = 4;
    public final static int NullTag = 5;
    public final static int OidTag = 6;
    public final static int SequenceTag = 0x30;

    ////////////////////////// PRIVATE ///////////////////////////////

    /**
    * Fetch a tag and move the current position forward.
    *
    * @return The tag
    */

    private final int fetchTag() throws BerException {
        int result = 0;
        final int backup = next;

        try {
            final byte b0 = bytes[next++];
            result = (b0 >= 0) ? b0 : b0 + 256;
            if ((result & 31) == 31) {
                while ((bytes[next] & 128) != 0) {
                    result = result << 7;
                    result = result | (bytes[next++] & 127);
                }
            }
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        }

        return result;
    }

    /**
    * Fetch a length and move the current position forward.
    *
    * @return The length
    */

    private final int fetchLength() throws BerException {
        int result = 0;
        final int backup = next;

        try {
            final byte b0 = bytes[next++];
            if (b0 >= 0) {
                result = b0;
            } else {
                for (int c = 128 + b0; c > 0; c--) {
                    final byte bX = bytes[next++];
                    result = result << 8;
                    result = result | ((bX >= 0) ? bX : bX + 256);
                }
            }
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        }

        return result;
    }

    /**
    * Fetch an integer value and move the current position forward.
    *
    * @return The integer
    */

    private int fetchIntegerValue() throws BerException {
        int result = 0;
        final int backup = next;

        try {
            final int length = fetchLength();
            if (length <= 0)
                throw new BerException();
            if (length > (bytes.length - next))
                throw new IndexOutOfBoundsException("Decoded length exceeds buffer");
            final int end = next + length;
            result = bytes[next++];
            while (next < end) {
                final byte b = bytes[next++];
                if (b < 0) {
                    result = (result << 8) | (256 + b);
                } else {
                    result = (result << 8) | b;
                }
            }
        } catch (BerException e) {
            next = backup;
            throw e;
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        } catch (ArithmeticException e) {
            next = backup;
            throw new BerException();
        }
        return result;
    }

    /**
    * Fetch an integer value and return a long value.
    * FIX ME: someday we could have only on fetchIntegerValue() which always
    * returns a long value.
    *
    * @return The integer
    */

    private final long fetchIntegerValueAsLong() throws BerException {
        long result = 0;
        final int backup = next;

        try {
            final int length = fetchLength();
            if (length <= 0)
                throw new BerException();
            if (length > (bytes.length - next))
                throw new IndexOutOfBoundsException("Decoded length exceeds buffer");

            final int end = next + length;
            result = bytes[next++];
            while (next < end) {
                final byte b = bytes[next++];
                if (b < 0) {
                    result = (result << 8) | (256 + b);
                } else {
                    result = (result << 8) | b;
                }
            }
        } catch (BerException e) {
            next = backup;
            throw e;
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        } catch (ArithmeticException e) {
            next = backup;
            throw new BerException();
        }
        return result;
    }

    /**
    * Fetch a byte string and move the current position forward.
    *
    * @return The byte string
    */

    private byte[] fetchStringValue() throws BerException {
        byte[] result = null;
        final int backup = next;

        try {
            final int length = fetchLength();
            if (length < 0)
                throw new BerException();
            if (length > (bytes.length - next))
                throw new IndexOutOfBoundsException("Decoded length exceeds buffer");
            final byte data[] = new byte[length];
            java.lang.System.arraycopy(bytes, next, data, 0, length);
            next += length;
            //      int i = 0 ;
            //      while (i < length) {
            //          result[i++] = bytes[next++] ;
            //      }
            result = data;
        } catch (BerException e) {
            next = backup;
            throw e;
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        } catch (ArithmeticException e) {
            next = backup;
            throw new BerException();
        }
        // catch(Error e) {
        //  debug("fetchStringValue: Error decoding BER: " + e);
        //  throw e;
        // }

        return result;
    }

    /**
    * Fetch an oid and move the current position forward.
    *
    * @return The oid
    */

    private final long[] fetchOidValue() throws BerException {
        long[] result = null;
        final int backup = next;

        try {
            final int length = fetchLength();
            if (length <= 0)
                throw new BerException();
            if (length > (bytes.length - next))
                throw new IndexOutOfBoundsException("Decoded length exceeds buffer");
            // Count how many bytes have their 8th bit to 0
            // -> this gives the number of components in the oid
            int subidCount = 2;
            for (int i = 1; i < length; i++) {
                if ((bytes[next + i] & 0x80) == 0) {
                    subidCount++;
                }
            }
            final int datalen = subidCount;
            final long[] data = new long[datalen];
            final byte b0 = bytes[next++];

            // bugId 4641746
            // The 8th bit of the first byte should always be set to 0
            if (b0 < 0)
                throw new BerException();

            // bugId 4641746
            // The first sub Id cannot be greater than 2
            final long lb0 = b0 / 40;
            if (lb0 > 2)
                throw new BerException();

            final long lb1 = b0 % 40;
            data[0] = lb0;
            data[1] = lb1;
            int i = 2;
            while (i < datalen) {
                long subid = 0;
                byte b = bytes[next++];
                while ((b & 0x80) != 0) {
                    subid = (subid << 7) | (b & 0x7f);
                    // bugId 4654674
                    if (subid < 0)
                        throw new BerException();
                    b = bytes[next++];
                }
                subid = (subid << 7) | b;
                // bugId 4654674
                if (subid < 0)
                    throw new BerException();
                data[i++] = subid;
            }
            result = data;
        } catch (BerException e) {
            next = backup;
            throw e;
        } catch (IndexOutOfBoundsException e) {
            next = backup;
            throw new BerException();
        }
        // catch(Error e) {
        //  debug("fetchOidValue: Error decoding BER: " + e);
        //  throw e;
        // }

        return result;
    }

    // private static final void debug(String str) {
    //   System.out.println(str);
    // }

    //
    // This is the byte array containing the encoding.
    //
    private final byte bytes[];

    //
    // This is the current location. It is the next byte
    // to be decoded. It's an index in bytes[].
    //
    private int next = 0;

    //
    // This is the stack where end of sequences are kept.
    // A value is computed and pushed in it each time openSequence()
    // is invoked.
    // A value is pulled and checked each time closeSequence() is called.
    //
    private final int stackBuf[] = new int[200];
    private int stackTop = 0;

}
